#!python3

"""
数据分析的基本过程
朝阳医院2018年销售数据分析
项目来源：https://zhuanlan.zhihu.com/p/35976466
"""

import pandas


def dataClean(file):
    """
    数据清洗
    :param file:
    :return:
    """

    """
    一、理解数据
    """
    xls = pandas.ExcelFile(file)  # 读取Ecxcel数据
    salesDf = xls.parse('Sheet1')
    # print("=================原始数据=================")
    # print(salesDf.head(5))  # 打印出前5行，以确保数据运行正常
    # print(salesDf.shape)  # 有多少行，多少列
    # print(salesDf.dtypes)  # 查看每列的数据类型

    """
    二、数据清洗
    """
    # 1、使用loc方法选择子集[起止行，起止列名]
    # subSalesDf = salesDf.loc[1:2, '购药时间':'销售数量']
    # print(subSalesDf)

    # 2.列名重命名：
    colNameDict = {'购药时间': '销售时间', '社保卡号': '社保卡号'}  # 将‘购药时间’改为‘销售时间’
    # inplace=False，数据框本身不会变，而会创建一个改动后新的数据框，默认的inplace是False，
    # inplace=True，数据框本身会改动
    salesDf.rename(columns=colNameDict, inplace=True)
    # print(salesDf.head())  # 查看前五行

    # 3.缺失数据处理：
    # python缺失值有3种：None，NA，NaN
    # 1）Python内置的None值
    # 2）在pandas中，将缺失值表示为NA，表示不可用not available。
    # 3）对于数值数据，pandas使用浮点值NaN（Not a Number）表示缺失数据。
    # 后面出来数据，如果遇到错误：......foloat错误，那就是有缺失值，需要处理掉

    # 删除列（销售时间，社保卡编号）中为空的行
    # how='any'意为在给定的任何一列中有缺失值就删除
    # 如果缺失数据太多，我们可以建立模型，使用插入值的方法来补充数据（以后机器学习的文章中介绍）
    # print('删除缺失值前有多少行，多少列', salesDf.shape)  # 有多少行，多少列
    salesDf = salesDf.dropna(subset=['销售时间', '社保卡号'], how='any')
    # print('删除缺失后大小', salesDf.shape)

    # 4.数据类型转换
    # 一开始导入时我们将所有数据都按字符串类型导入的，
    # 现在需要将销售数量、应收金额、实收金额的数据类型改为数值类型。
    salesDf['销售数量'] = salesDf['销售数量'].astype('float')
    salesDf['应收金额'] = salesDf['应收金额'].astype('float')
    salesDf['实收金额'] = salesDf['实收金额'].astype('float')
    salesDf['社保卡号'] = salesDf['社保卡号'].astype('string')
    salesDf['商品编码'] = salesDf['商品编码'].astype('string')
    # print('转换后的数据类型：\n', salesDf.dtypes)

    # 修改日期格式
    timeSer = salesDf.loc[:, '销售时间']  # 获取“销售时间”这一列
    dateSer = splitTime(timeSer)  # 对字符串进行分割，获取销售日期
    salesDf.loc[:, '销售时间'] = dateSer  # 修改销售时间这一列的值

    # 修改社保卡号显示样式
    sbhSer = salesDf.loc[:, '社保卡号']  # 获取“社保卡号”这一列
    khSer = parseStr(sbhSer)  # 对字符串进行分割，获取社保卡号
    salesDf.loc[:, '社保卡号'] = khSer  # 修改社保卡号这一列的值

    # 修改商品编号显示样式
    sphSer = salesDf.loc[:, '商品编码']  # 获取“商品编码”这一列
    spSer = parseStr(sphSer)  # 对字符串进行分割，获取商品编码
    salesDf.loc[:, '商品编码'] = spSer  # 修改商品编码这一列的值

    # 5.字符串转换日期
    # 使用pd.to_datetime方法来将字符串转换为日期格式。
    # 传入的格式是原始数据的日期格式——format = '%Y-%m-%d'
    # errors = 'coerce'： 如果原始数据不符合日期的格式，转换后的值为空值NaT
    salesDf.loc[:, '销售时间'] = pandas.to_datetime(salesDf.loc[:, '销售时间'],
                                                format='%Y-%m-%d',
                                                errors='coerce')
    # print(salesDf.dtypes)

    # 转换之后我们还要运行一次删除空值的代码，因为不符合格式的日期被转变为了空值需要删除。
    salesDf = salesDf.dropna(subset=['销售时间', '社保卡号'], how='any')

    # 6.数据排序
    # 使用pd.sort_values方法对数据进行排序，by表示按那几列进行排序，
    # ascending = True表示升序排列，ascending = False表示降序排列
    # print('排序前的数据集')
    # print(salesDf.head())
    salesDf = salesDf.sort_values(by=['销售时间', '社保卡号'], ascending=True)
    # print('排序后的数据集')
    # print(salesDf.head(3))

    # 重命名行号：reset_index方法生成从0到N按顺序的索引值
    salesDf = salesDf.reset_index(drop=True)
    # print(salesDf.head(5))

    # 7.异常值处理
    # 首先用describe()方法查看数据框中所有数据每列的描述统计信息：
    # （count：总数，mean：平均数，std：标准差，min：最小值，
    # 25 %：下四分位数，50 %：中位数，75 %：上四分位数，max：最大值）
    # print(salesDf.describe())

    # 发现最小值出现了小于0的情况，分析应该是记录过程中出现错误所致。
    # 接下来删除异常值：通过条件判断筛选出销售数量大于0的数据
    # 设置查询条件
    querySer = salesDf.loc[:, '销售数量'] > 0
    # 应用查询条件
    salesDf = salesDf.loc[querySer, :]

    # print("=================清洗后的数据=================")
    # print(salesDf.head(5))

    return salesDf

    # print(salesDf.describe())


def splitTime(timeColSer):
    """
    时间分隔函数：将[2018-01-01 星期五]时间格式转换为[2018-01-01]时间格式
    :param timeColSer:
    :return:
    """
    timeList = []
    for value in timeColSer:  # 例如2018-01-01 星期五，分割后为：2018-01-01
        dateStr = value.split(' ')[0]
        timeList.append(dateStr)
    timeSer = pandas.Series(timeList)  # 将列表转行为一维数据Series类型
    return timeSer


def parseStr(bh):
    """
    处理字符串：社保卡号和商品编码类型转换后显示为小数，需要处理为字符串
    :return:
    """
    strList = []
    for value in bh:  # 例如2018-01-01 星期五，分割后为：2018-01-01
        dateStr = value.split('.')[0]
        strList.append(dateStr)
    bhStr = pandas.Series(strList)  # 将列表转行为一维数据Series类型
    return bhStr


def buildingModels(salesDf):
    """
    构建模型
    :param salesDf:
    :return:
    """
    kpi_1 = kpi1(salesDf)
    print('月均消费次数=', kpi_1[0])

    kpi_2 = kpi2(salesDf, kpi_1[1])
    print('月均消费金额=', kpi_2[0])

    print('平均交易金额=', kpi3(kpi_2[1], kpi_1[2]))

    kpi4(salesDf)


def kpi1(salesDf):
    """
    第一个指标：月均消费次数 = 总消费次数 / 月数
    :param salesDf:
    :return:
    """
    # 注意：同一天内，同一个人发生的所有消费算作一次消费，
    # 根据列名（销售时间，社区卡号），如果这两个列值同时相同，只保留1条，
    # 使用drop_duplicates将重复的数据删除
    kpi1_Df = salesDf.drop_duplicates(
        subset=['销售时间', '社保卡号']
    )

    totalI = kpi1_Df.shape[0]  # 总消费次数————有多少行

    # print('总消费次数=', totalI)

    # 计算月份数我们要知道最早一笔消费的时间和最晚一笔消费的时间：
    # 第1步：按销售时间升序排序
    kpi1_Df = kpi1_Df.sort_values(by='销售时间', ascending=True)
    kpi1_Df = kpi1_Df.reset_index(drop=True)  # 重命名行名（index）

    # 第2步：获取时间范围
    startTime = kpi1_Df.loc[0, '销售时间']  # 最小时间值
    endTime = kpi1_Df.loc[totalI - 1, '销售时间']  # 最大时间值

    # 第3步：计算月份数
    daysI = (endTime - startTime).days  # 天数
    monthsI = daysI // 30  # 月份数: 运算符“//”表示取整除，返回商的整数部分，例如9//2 输出结果是4
    # print('月份数：', monthsI)

    # 最终计算月均消费次数 = 总消费次数 / 月份数
    kpi1_I = totalI // monthsI
    # print('月均消费次数=', kpi1_I)

    return [kpi1_I, monthsI, totalI]


def kpi2(salesDf, monthsI):
    """
    第二个指标：月均消费金额 = 总消费金额 / 月份数
    :param salesDf:
    :param monthsI:
    :return:
    """
    totalMoneyF = salesDf.loc[:, '实收金额'].sum()  # 总消费金额
    monthMoneyF = totalMoneyF / monthsI  # 月均消费金额
    # print('业务指标2：月均消费金额=', monthMoneyF)

    return [monthMoneyF, totalMoneyF]


def kpi3(totalMoneyF, totalI):
    """
    第三个指标：平均交易金额=总消费金额/总消费次数
    :param totalMoneyF: 总消费金额
    :param totalI: 总消费次数
    :return:
    """
    pct = totalMoneyF / totalI
    # print('平均交易金额：', pct)

    return pct


def kpi4(salesDf):
    """
    第四个指标：月度消费趋势
    :return:
    """

    # 在进行操作之前，先把数据复制到另一个数据框中，防止对之前清洗后的数据框造成影响
    groupDf = salesDf

    # 第1步：重命名行名（index）为销售时间所在列的值
    groupDf.index = groupDf['销售时间']
    # print(groupDf.head())

    # 第2步：按月分组
    gb = groupDf.groupby(groupDf.index.month)

    # 第3步：应用函数，计算每个月的消费总额
    mounthDf = gb.sum()

    print(mounthDf)


if __name__ == '__main__':
    salesDf = dataClean('./朝阳医院2018年销售数据.xlsx')
    # print(salesDf.describe())

    buildingModels(salesDf)
